﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Reflection;
using System.Web.UI;
using Ext.Net;
using Ext.Net.Utilities;


namespace KamLib
{

    public class LogicNodeAttribute : Attribute
    {
        public string LID;
        public LogicNodeAttribute(string lid)
        {
            LID = lid;
        }
    }


    public class UiLogicContext : IDisposable
    {
        public readonly Dictionary<string, UiLogicController> Controllers = new Dictionary<string, UiLogicController>();

        public readonly HashSet<string> ReloadedStores = new HashSet<string>();
        public LogWriter Log;

        public UiLogicController GetController(string lid)
        {
            var node = Controllers.Try(lid);
            if (node == null)
                Controllers[lid] = node = UiLogicController.NewController(this, lid);
            return node;
        }
        public T GetNode<T>() where T : UiLogicController, new()
        {
            var node = Controllers.Values.OfType<T>().FirstOrDefault();
            if (node == null)
            {
                var nodeType = UiLogicController.ControllerTypes.Where(a => a.Value == typeof(T)).First();
                Controllers[nodeType.Key] = node = (T)new T().Initialize(this);
            }
            return node;
        }

        public UiLogicNode GetLogic(UiKey key)
        {
            if (key == null) return null;
            var node = GetController(key.LID);
            if (node == null) return null;
            return (UiLogicNode)node.GetForm(key) ?? node.GetList(key);
        }

        public FormLogic GetForm(UiKey key)
        {
            if (key == null) return null;
            var node = GetController(key.LID);
            if (node == null) return null;
            return node.GetForm(key);
        }
        public ListLogic GetList(UiKey key)
        {
            if (key == null) return null;
            var node = GetController(key.LID);
            if (node == null) return null;
            return node.GetList(key);
        }

        public EntityDescAttribute GetEntityDesc(UiKey key)
        {
            var logic = GetLogic(key);
            return logic == null ? null : logic.EntityDesc;
        }

        public void Dispose()
        {
            foreach (var node in Controllers.Values)
            {
                node.Dispose();
            }
        }
    }


    public class UiFormLogicNodeInfo : IDisposable
    {
        public UiLogicController Node;
        public string LIID;
        public Type Type;
        public string[] KeyFields;
        public Dictionary<UiKey, FormLogic> Items;
        public FormLogic GetForm(UiKey key)
        {
            return Node.GetForm(this, key);
        }


        public void Dispose()
        {
            if (Items == null) return;
            foreach (var item in Items.Values)
            {
                item.Dispose();
            }
        }
    }
    public class UiListLogicNodeInfo
    {
        public UiLogicController Node;
        public string LIID;
        public Type Type;
        public Dictionary<string, ListLogic> Items;
        public ListLogic GetList(string id = "0")
        {
            return Node.GetList(this, id);
        }

        public void Dispose()
        {
            if (Items == null) return;
            foreach (var item in Items.Values)
            {
                item.Dispose();
            }
        }

    }



    public abstract class UiLogicController : IDisposable
    {
        public UiLogicContext Context;

        public string LID;
        public string VirtualPath;

        public readonly Dictionary<string, UiFormLogicNodeInfo> FormInfos = new Dictionary<string, UiFormLogicNodeInfo>();
        public readonly Dictionary<string, UiListLogicNodeInfo> ListInfos = new Dictionary<string, UiListLogicNodeInfo>();


        protected static uint LastControlID;
        public virtual string GetNextControlID() { return GetNextControlID(ref LastControlID); }

        public string GetNextControlID(ref uint lastId)
        {
#if DEBUG
            return Guid.NewGuid().ToString().Replace("-", "");
#else
            return unchecked(lastId++).ToString("X");
#endif
        }

        /// <summary>
        /// Используется для создания/поиска LogicNodes по LID
        /// </summary>
        public static readonly Dictionary<string, Type> ControllerTypes = new Dictionary<string, Type>();
        /// <summary>
        /// Регистрирует все классы LogicNode в NodeTypes, который затем используется для создания nodes по LID
        /// </summary>
        /// <param name="types"></param>
        public static void Register(IEnumerable<Type> types)
        {
            foreach (var type in types.Where(a => typeof(UiLogicController).IsAssignableFrom(a)))
            {
                var attr = (LogicNodeAttribute)type.GetCustomAttributes(typeof(LogicNodeAttribute), true).FirstOrDefault();
                if (attr != null)
                    ControllerTypes[attr.LID] = type;
            }
        }
        public static void Register(Assembly assembly)
        {
            Register(assembly.GetTypes());
        }

        public static UiLogicController NewController(UiLogicContext context, string lid)
        {
            var t = ControllerTypes.Try(lid);
            return t != null ? ((UiLogicController)Activator.CreateInstance(t)).Initialize(context) : null;
        }

        //protected virtual void Init() { }

        protected void InitNodes(Type baseType)
        {
            foreach (var type in baseType.GetNestedTypes())
            {
                if (type.IsSubclassOf(typeof(ListLogic)))
                {
                    if (type.IsAbstract) continue;
                    var id = type.Name.TrimEnd(false, "Logic");
                    ListInfos.Add(id, new UiListLogicNodeInfo { Node = this, LIID = id, Type = type });
                }
                else if (type.IsSubclassOf(typeof(FormLogic)))
                {
                    if (type.IsAbstract) continue;
                    var id = type.Name.TrimEnd(false, "Logic");
                    var getKeyFields = type.GetMethod("GetKeyFields", BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.Static);
                    FormInfos.Add(id, new UiFormLogicNodeInfo
                    {
                        Node = this,
                        LIID = id,
                        Type = type,
                        KeyFields = getKeyFields == null ? null : (string[])getKeyFields.Invoke(null, null),
                    });
                }
                else
                {
                    InitNodes(type);
                }
            }
        }


        public UiLogicController Initialize(UiLogicContext context)
        {
            Context = context;
            //Init();

            var attr = (LogicNodeAttribute)GetType().GetCustomAttributes(typeof(LogicNodeAttribute), true).FirstOrDefault();
            if (attr != null)
                LID = attr.LID;

            InitNodes(GetType());
            InitCheck();

            return this;
        }

        public void Register(Page page)
        {
            var rm = ResourceManager.GetInstance(page);
            rm.AddAfterClientInitScript(
                "var l = KamLib.{0} = KamLib.{0} || {{}}; l.grids = {{}}; l.virtualPath = '{1}';".FormatWith(
                LID, VirtualPath
            ));
        }


        //public abstract void Init();
        public void InitCheck()
        {
            if (LID.isnull())
                throw new Exception("The property 'LID' must be set.");
        }


        #region GetForm

        public FormLogic GetForm(UiFormLogicNodeInfo node, UiKey key)
        {
            if (node.Items != null)
            {
                var o1 = node.Items.Try(key);
                if (o1 != null) return o1;
            }
            else
                node.Items = new Dictionary<UiKey, FormLogic>();

            var o = (FormLogic)Activator.CreateInstance(node.Type);
            o.KeyFields = node.KeyFields;
            o.Initialize(this, node.LIID, key);
            node.Items.Add(key, o);

            var list = ListInfos.Try(node.LIID.TrimEnd(false, "Form") + "List");
            if (list != null)
            {
                if (o.DependentKeys == null) o.DependentKeys = new List<UiKey>();
                UiKey dkey = null;
                if (key.MasterKey != null && key.MasterKey.DetailKeys != null)
                    dkey = key.MasterKey.DetailKeys.FirstOrDefault(a => a.LID == LID && a.LIID == list.LIID);
                o.DependentKeys.Add(dkey ?? new UiKey { LID = LID, LIID = list.LIID });
            }
            return o;
        }

        public FormLogic GetForm(Type formLogicType, UiKey key)
        {
            var ltype = FormInfos.FirstOrDefault(a => a.Value.Type == formLogicType).Value;
            if (ltype == null)
            {
                FormInfos.Add(
                    formLogicType.Name.TrimEnd(false, "Logic"),
                    ltype = new UiFormLogicNodeInfo { Node = this, LIID = key.LIID, Type = formLogicType }
                );
            }

            return GetForm(ltype, key);
        }

        public FormLogic GetForm(UiKey key)
        {
            return key != null && key.LIID.IsNotEmpty() && FormInfos.ContainsKey(key.LIID)
                ? GetForm(FormInfos[key.LIID], key) : null;
        }

        public T GetForm<T>(UiKey key) where T : FormLogic
        {
            return (T)GetForm(typeof(T), key);
        }

        #endregion


        #region GetList

        public ListLogic GetList(UiListLogicNodeInfo logicType, UiKey key)
        {
            if (logicType.Items != null)
            {
                var o1 = logicType.Items["0"];
                if (o1 != null) return o1;
            }
            else
                logicType.Items = new Dictionary<string, ListLogic>();

            var o = (ListLogic)Activator.CreateInstance(logicType.Type);
            o.Initialize(this, logicType.LIID, key);
            logicType.Items.Add("0", o);

            return o;
        }
        //public ListLogic GetList(Type ListLogicType, string id = "0")
        //{
        //    var ltype = ListNodes.FirstOrDefault(a => a.Value.Type == ListLogicType).Value;
        //    if (ltype == null)
        //    {
        //        ListNodes.Add(
        //            ListLogicType.Name.TrimEnd(false, "Logic"),
        //            ltype = new ListLogicNode { LIID = id, Type = ListLogicType }
        //        );
        //    }

        //    return GetList(ltype, id);
        //}
        public ListLogic GetList(string liid, UiKey key)
        {
            var info = ListInfos.Try(liid);
            if (info == null)
                throw new Exception("Can't find ListLogic \"" + (key != null ? key.LID + "." : null) + liid + "\"");
            return GetList(info, key);
        }
        //public ListLogic GetList(BiKey[] existsKeys, string liid, string id = "0")
        //{
        //    var item = GetList(ListNodes[liid], id);
        //    if (item != null && existsKeys != null)
        //    {
        //        var dkey = existsKeys.FirstOrDefault(a => a.LIID == liid);
        //        if (dkey != null) item.Key = dkey;
        //    }
        //    return item;
        //}
        //public T GetList<T>(string id = "0") where T : ListLogic
        //{
        //    return (T)GetList(typeof(T), id);
        //}

        public ListLogic GetList(UiKey key = null)
        {
            UiListLogicNodeInfo logic;
            if (key != null)
                logic = ListInfos[key.LIID];
            else
            {
                logic = ListInfos.Values.First();
                key = new UiKey { LID = LID, LIID = logic.LIID, CID = LID };
            }
            return GetList(logic, key);
        }


        #endregion

        public void Dispose()
        {
            foreach (var node in FormInfos.Values)
            {
                node.Dispose();
            }
            foreach (var node in ListInfos.Values)
            {
                node.Dispose();
            }
        }
    }


}